﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using AwesomeEnginePC.Physics;

namespace AwesomeEnginePC.Inputs
{
    /// <summary>
    /// Represents any input device (keyboard, mouse, gamepad, etc)
    /// </summary>
    /// <typeparam name="T">An enum type for binding names</typeparam>
    public abstract class BaseInputType<T>
    {
        public BaseInputType()
        {
            if (!typeof(T).IsEnum)
                throw new Exception("Generic type was not an enum. Use an enum for tracking bindings.");

            Bindings = new Dictionary<T, BaseInputButton>();
        }

        /// <summary>
        /// Returns all tracked binding names
        /// </summary>
        /// <returns></returns>
        public List<T> AllBindingNames
        {
            get { return Bindings.Keys.ToList(); }
        }

        /// <summary>
        /// Returns all bindings that are pressed (button down)
        /// </summary>
        public List<T> PressedBindings { get; private set; }

        /// <summary>
        /// Returns all bindings that were just pressed
        /// </summary>
        public List<T> JustPressedBindings { get; private set; }

        /// <summary>
        /// Returns all bindings that were just released
        /// </summary>
        public List<T> JustReleasedBindings { get; private set; }

        protected Dictionary<T, BaseInputButton> Bindings { get; set; }

        public void ChangeMapping(T bindingName, BaseInputButton newButton)
        {
            Bindings[bindingName] = newButton;
        }

        public void ClearBindings()
        {
            Bindings.Clear();
        }

        protected abstract void UpdatePreviousState();
        protected abstract bool IsButtonDown(BaseInputButton btn);
        protected abstract bool WasPreviouslyDown(BaseInputButton btn);

        /// <summary>
        /// Finds all titles for an input button
        /// </summary>
        /// <param name="btn"></param>
        /// <returns></returns>
        public List<T> MappingLookup(BaseInputButton btn)
        {
            return Bindings
                .Where((x) => x.Value.Button == btn)
                .Select(x => x.Key)
                .ToList();
        }

        /// <summary>
        /// Returns the button from the given title
        /// </summary>
        /// <param name="title"></param>
        /// <returns></returns>
        public BaseInputButton MappingLookup(T bindingName)
        {
            return Bindings[bindingName];
        }

        public void RemoveAllOfButton(BaseInputButton btn)
        {
            foreach (var item in Bindings.Where((x) => x.Value == btn).ToList())
            {
                Bindings.Remove(item.Key);
            }
        }

        public void RemoveButton(T bindingName)
        {
            Bindings.Remove(bindingName);
        }

        /// <summary>
        /// Updates the input type, refreshing the stored previous values. Call this before checking for input.
        /// </summary>
        public void Update()
        {
            //gather just pressed and released values, then update previous values

            PressedBindings = Bindings
                    .Where((x) => IsButtonDown(x.Value))
                    .Select((x) => x.Key)
                    .ToList();

            JustPressedBindings = Bindings
                    .Where((x) => IsButtonDown(x.Value) && !WasPreviouslyDown(x.Value))
                    .Select((x) => x.Key)
                    .ToList();

            JustReleasedBindings = Bindings
                   .Where((x) => !IsButtonDown(x.Value) && WasPreviouslyDown(x.Value))
                   .Select((x) => x.Key)
                   .ToList();

            UpdatePreviousState();
        }

        protected void AddBinding(T bindingName, BaseInputButton btn)
        {
            if (Bindings.ContainsKey(bindingName))
                throw new Exception("There is already a button tracked with that name");

            Bindings.Add(bindingName, btn);
        }
    }

    public class InputTypeGamepad<T> : BaseInputType<T>
    {
        GamePadState previousState;

        public InputTypeGamepad(PlayerIndex index)
        {
            PlayerIndex = index;
        }

        public bool IsConnected { get { return GetState().IsConnected; } }

        public PlayerIndex PlayerIndex { get; set; }

        public GamePadThumbSticks ThumbSticks { get { return GetState().ThumbSticks; } }

        public GamePadTriggers Triggers { get { return GetState().Triggers; } }

        public void AddBinding(T bindingName, Buttons button)
        {
            AddBinding(bindingName, new InputGamePadButton(button));
        }

        protected override bool IsButtonDown(BaseInputButton btn)
        {
            if (!(btn is InputGamePadButton))
                throw new Exception("Button was not of the correct format. Expected InputGamePadButton");

            Buttons gpButton = ((InputGamePadButton)btn).GamePadButton;

            return GetState().IsButtonDown(gpButton);
        }

        private GamePadState GetState()
        {
            return GamePad.GetState(PlayerIndex);
        }

        protected override void UpdatePreviousState()
        {
            previousState = GamePad.GetState(PlayerIndex);
        }

        protected override bool WasPreviouslyDown(BaseInputButton btn)
        {
            InputGamePadButton gpBtn = (InputGamePadButton)btn;
            return previousState.IsButtonDown(gpBtn.GamePadButton);
        }
    }

    public class InputTypeKeyboard<T> : BaseInputType<T>
    {
        KeyboardState previousState;

        public void AddBinding(T bindingName, Keys key)
        {
            AddBinding(bindingName, new InputKeyboardButton(key));
        }

        protected override bool IsButtonDown(BaseInputButton btn)
        {
            if (!(btn is InputKeyboardButton))
                throw new Exception("Button was not of the correct format. Expected InputTypeKeyboard");

            InputKeyboardButton keyButton = (InputKeyboardButton)btn;

            KeyboardState keyState = Keyboard.GetState();
            return keyState.IsKeyDown(keyButton.Key);
        }

        protected override void UpdatePreviousState()
        {
            previousState = Keyboard.GetState();
        }

        protected override bool WasPreviouslyDown(BaseInputButton btn)
        {
            InputKeyboardButton keyBtn = (InputKeyboardButton)btn;
            return previousState.IsKeyDown(keyBtn.Key);
        }
    }

    public class InputTypeMouse<T> : BaseInputType<T>
    {
        MouseState previousState;

        public Vector<int> MousePosition
        {
            get
            {
                MouseState ms = Mouse.GetState();
                return new Vector<int>(ms.X, ms.Y);
            }
        }

        public int ScrollWheelPosition
        {
            get
            {
                MouseState ms = Mouse.GetState();
                return ms.ScrollWheelValue;
            }
        }

        public void AddBinding(T bindingName, MouseButton btn)
        {
            AddBinding(bindingName, new InputMouseButton(btn));
        }

        protected override bool IsButtonDown(BaseInputButton btn)
        {
            if (!(btn is InputMouseButton))
                throw new Exception("Button was not of the correct format. Expected InputMouseButton");

            InputMouseButton mouseButton = (InputMouseButton)btn;
            MouseState mouseState = Mouse.GetState();

            //special code for wheel movement, check if the previous value is in the correct direction
            if (mouseButton.MButton == MouseButton.ScrollWheelForward)
            {
                //(current value - previous value = 1)
                return (mouseState.ScrollWheelValue - previousState.ScrollWheelValue) > 0;
            }
            else if (mouseButton.MButton == MouseButton.ScrollWheelBack)
            {
                return (mouseState.ScrollWheelValue - previousState.ScrollWheelValue) < 0;
            }
            else
            {
                ButtonState btnState = (ButtonState)mouseState.GetType().GetProperty(mouseButton.MButton.ToString()).GetValue(mouseState, null);
                return btnState == ButtonState.Pressed;
            }           
        }

        protected override void UpdatePreviousState()
        {
            previousState = Mouse.GetState();
        }

        protected override bool WasPreviouslyDown(BaseInputButton btn)
        {
            InputMouseButton mouseBtn = (InputMouseButton)btn;

            if (mouseBtn.MButton == MouseButton.ScrollWheelForward || mouseBtn.MButton == MouseButton.ScrollWheelBack)
            {
                return false;
            }

            ButtonState previousButtonState = (ButtonState)previousState.GetType().GetProperty(mouseBtn.MButton.ToString()).GetValue(previousState, null);
            return previousButtonState == ButtonState.Pressed;
        }
    }
}